﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using PowerAwareBluetooth.Model;
using InTheHand.Net.Bluetooth;

namespace PowerAwareBluetooth.Controller.AI
{
    internal class DecisionMaker
    {
        #region /// Members ///

        private Learner m_learner;
        private CellTowerLearner m_cellTowerLearner = new CellTowerLearner();
        
        private BluetoothAdapter m_bluetoothAdapter;
        private BatteryAdapter m_BatteryAdapter = new BatteryAdapter();
        private RIL m_cellTowerAdapter = new RIL();
        
        private RuleList m_rules;
        
        private bool m_enableLearning = true;
        private DateTime m_waitAfterUserControl = DateTime.MinValue;
        private DateTime m_lastSampleTime = DateTime.MinValue;
        private DateTime m_nextSample = DateTime.MinValue;
        //private int m_waitTimeBetweenSamples = BluetoothAdapterConstants.Learning.DEFAULT_WAITING_TIME_BETWEEN_SAMPLES;
        private int m_resultsCounter = 0;
        private int m_cellResultsCounter = 0;
        private string m_lastCellId = null;

        #endregion

        public DecisionMaker(BluetoothAdapter bluetoothAdapter, RuleList ruleList)
        {
            m_learner = new Learner();
            m_bluetoothAdapter = bluetoothAdapter;
            RulesList = ruleList;
        }

        /// <summary>
        /// gets or sets the user-defined rule list
        /// </summary>
        public RuleList RulesList
        {
            get { return m_rules; }
            set { m_rules = value; }
        }

        /// <summary>
        /// return the decision maker decision (what radio mode should the bluetooth be in)
        /// </summary>
        /// <returns></returns>
        public RadioMode RadioModeDecided()
        {   
            RadioMode res;
            if (DateTime.Now < m_waitAfterUserControl)
            {
                res = m_bluetoothAdapter.RadioMode;
            }
            else
            {
                //check if user defined rule for the current time
                Rule rule = RulesList.GetRule(DateTime.Now);

                if (rule != null)
                {
                    if(rule.RuleAction == RuleActionEnum.TurnOn)
                    {
                        res = RadioMode.Discoverable;
                    }
                    else
                    {
                        res = RadioMode.PowerOff;
                    }
                    
                }
                else
                {
                    //no rule - go according to learner and battery power
                    res = RadioMode.PowerOff;
                    if (m_learner.IsLearntBehaviourOn())
                    {
                        //listen to learner only if battery is not low or battery is charging
                        if (!m_BatteryAdapter.BatteryLow || m_BatteryAdapter.BatteryCharching)
                        {
                            res = RadioMode.Discoverable;
                        }
                    }
                }

            }
            
            return res;
        }

        /// <summary>
        /// sample for other bluetooth and learn the result
        /// </summary>
        public void Sample(long samplesPerInterval)
        {
            //check if we are at a new learning interval
            if (!m_learner.SameInterval(m_lastSampleTime, DateTime.Now) && m_lastSampleTime.Ticks != DateTime.MinValue.Ticks)
            {
                //last interval results are evaluated and learnt    
                bool result = (m_resultsCounter >= 0);
                
                m_learner.Learn(result, m_learner.CurrentInterval() - 1);
                m_resultsCounter = 0;
            }

            UpdateCellInfo();

            m_lastSampleTime = DateTime.Now;
            m_nextSample = NextSampleTime(samplesPerInterval);

            if (m_bluetoothAdapter.SampleForOtherBluetooth())
            {
                //positive example
                m_resultsCounter++;
                m_cellResultsCounter++;
            }
            else
            {
                //negative example
                m_resultsCounter--;
                m_cellResultsCounter--;
            }
        }

      

        /// <summary>
        /// is sample needed
        /// </summary>
        /// <returns>true if a new sample time has come. false otherwise</returns>
        public bool SampleNeeded()
        {
            if (m_lastSampleTime == null || m_lastSampleTime.Ticks == DateTime.MinValue.Ticks)
            {
                return true;
            }
           
            return (m_nextSample.Ticks <= DateTime.Now.Ticks);
        }

        /// <summary>
        /// enable or disable the learning mechanism
        /// one should set this flag to false if he's about to activate the bluetooth
        /// and remember to turn back on after bluetooth is activated
        /// </summary>
        public bool EnableLearning
        {
            get
            {
                return m_enableLearning;
            }
            set
            {
                m_enableLearning = value;
            }
        }

        /// <summary>
        /// called when the radio mode was changed
        /// </summary>
        public void LearnUserChangedMode(RadioMode userCommandedMode)
        {
            //if radio mode changed to discoverable then learn - "user uses his bluetooth"
            if (userCommandedMode == RadioMode.Discoverable)
            {
                m_learner.Learn(true, m_learner.CurrentInterval());
            }
            else //radio mode is either off or connectable. learn - "user doesn't uses his bluetooth"
            {
                m_learner.Learn(false, m_learner.CurrentInterval());
            }

            //don't touch bluetooth for next Constant minutes
            m_waitAfterUserControl = DateTime.Now.AddMinutes(BluetoothAdapterConstants.Learning.MinimumTimeAfterUserExplicitControl);
            
        }

        /// <summary>
        /// reset the learning machine
        /// </summary>
        public void ResetLearning()
        {
            m_learner.ResetLearntBehaviour();
        }

        /// <summary>
        /// calculates the number of milliseconds that the manager
        /// should wait until asking for the next action
        /// </summary>
        /// <returns>the number of milliseconds to sleep</returns>
        public long CalculateCurrentWaitTime(long samplesPerInterval)
        {
            DateTime nextSample = this.NextSampleTime(samplesPerInterval);

            TimeSpan diffSpan = nextSample.Subtract(DateTime.Now);

            return (diffSpan.Ticks / TimeSpan.TicksPerMillisecond);
        }

        /// <summary>
        /// return the recommended samples per interval.
        /// recommendation is given according to statistics gathered on current cellphone cell
        /// </summary>
        /// <returns></returns>
        public long RecommendedSamplesPerInterval()
        {
            string cellId;
            if(!RIL.GetCellTowerInfo(out cellId))
            {
                return 1;
            }
            return (int)m_cellTowerLearner.BluetoothStatusInCell(cellId);
        }

        #region Threads
        private ManualResetEvent m_RuleInterruptEvent = new ManualResetEvent(false);

        // reset    --> state = not signaled --> threads will block
        // set      --> state = signaled     --> releases waiting threads

   
        public void ResetInterruptEvent()
        {
            // the following reset will cause the calling thread to block when calling waitone
            m_RuleInterruptEvent.Reset();
        }

        /// <summary>
        /// waits for a <see cref="m_RuleInterruptEvent"/> to occur. 
        /// </summary>
        /// <param name="timeout">the timeout in milliseconds to wait for the
        /// event</param>
        public void Sleep(long timeout)
        {
            m_RuleInterruptEvent.WaitOne((int)timeout, false);
        }


        /// <summary>
        /// wakes up threads that are waiting on <see cref="m_RuleInterruptEvent"/>
        /// </summary>
        public void WakeUp()
        {
            m_RuleInterruptEvent.Set();
        }

        #endregion Threads

        private DateTime NextSampleTime(long samplesPerInterval)
        {
            DateTime nextLearnInt = m_learner.NextLearningInterval();
            DateTime currIntStart = m_learner.CurrentIntervalBeginning();
            DateTime now = DateTime.Now;

            double minutesPerSample = (double)((double)m_learner.LearningIntervalLength / (double)samplesPerInterval);
            long ticksPerSample = (long)(minutesPerSample * TimeSpan.TicksPerMinute);

            if (nextLearnInt.Ticks <= now.Ticks)
            {
                //we are passed the next learning interval
                return DateTime.Now;
            }

            while (currIntStart.Ticks < now.Ticks)
            {
                currIntStart = currIntStart.AddTicks(ticksPerSample);
            }

            return currIntStart;
        }

        private void UpdateCellInfo()
        {
            string currCell;
            if (RIL.GetCellTowerInfo(out currCell))
            {
                if (currCell != m_lastCellId)
                {
                    //we switched cells. learn the results of the last cell
                    bool learntBehaviour = m_cellResultsCounter >= 0;
                    if (m_lastCellId != null)
                    {
                        m_cellTowerLearner.LearnBluetoothStatusInCell(m_lastCellId, learntBehaviour);
                    }
                    m_cellResultsCounter = 0;
                    m_lastCellId = currCell;
                }
            }
        }
    }
}